# -*- coding: utf-8 -*-

import os.path, sys

newpath = os.path.abspath('../..')
if not newpath in sys.path:
    sys.path.append(newpath)

from smally.uri import URL

class IncompleteRequest(Exception):
    pass

class IncompleteChunkedRequest(IncompleteRequest):
    pass

class ContinueRequest(Exception):
    pass

class Redirect(Exception):
    pass

class Request:
    
    def __init__(self, source=b'', url=URL(), method=b'HEAD',
                 version=b'HTTP/1.1', content=b'', headers={}, stream=False):
        self.url = url
        self.method = method
        self.version = version
        self.content = content
        self.headers = headers
        self.source = source
        self.stream = stream
        self.step = 0
        self.curdata = b''
        self.chunked = False
        self.contentlen = 0
        self.newstep = False
        self.allchunks = False
        if source:
            self.feed(source)
            
    def feed(self, source, last=False):
        for char in (bytes(i) for i in source):
            print('step', step)
            if self.step == 0:
                # Get method
                if char == b' ':
                    self.method = self.curdata
                    self.curdata = b''
                    self.step = 1
                else:
                    self.curdata += char
            elif self.step == 1:
                # Get path
                if char == b' ':
                    self.version = self.curdata
                    self.curdata = b''
                    self.step = 2
                else:
                    self.curdata += char
            elif self.step == 2:
                # Get version
                if char == b'\r':
                    self.version = self.curdata
                    self.curdata = b''
                elif char == b'\n':
                    self.step = 3
                else:
                    self.curdata += char
            elif self.step == 3:
                # Get headers name
                if char == b'\r' and not self.curdata:
                    continue
                elif char == b'\n' and not self.curdata:
                    if self.chunked:
                        self.step = 6
                    else:
                        self.step = 5
                elif char == b':':
                    self.curdata = [self.curdata, b'']
                    self.step = 4
                else:
                    self.curdata += char
            elif self.step == 4:
                # Get headers value
                if char == b'\r':
                    header = self.curdata[0].strip()
                    value = self.curdata[1].strip()
                    if header in self.headers:
                        self.headers[header].append(value)
                    else:
                        self.headers[header] = [value]
                    self.curdata = b''
                    if (header, value) ==\
                       (b'Transfer-Encoding', b'chunked'):
                        self.chunked = True
                elif char == b'\n':
                    self.step = 3
                else:
                    self.curdata[1] += char
            elif self.step == 5:
                # Get content
                if self.breakindex == 4:
                    last = True
                    self.content = self.content[:-2]
                    break
                elif char == b'\r' and not self.curdata:
                    self.breakindex += 1
                    self.content += char
                elif char == b'\n' and not self.curdata:
                    self.breakindex += 1
                    self.content += char
                else:
                    self.breakindex = 0
                    self.content += char
            elif self.step == 6:
                if char == b'\r' and not self.curdata:
                    break
                elif char in [b';', b'\r']:
                    self.newstep = True
                    chunklen = int(self.curdata, 16)
                    if chunklen:
                        self.contentlen += chunklen
                    else:
                        self.allchunks = True
                        self.step = 3
                    self.curdata = b''
                elif char == b'\n' and self.newstep:
                    self.newstep = False
                    self.step = 7
                else:
                    self.curdata += char
            elif self.step == 7:
                if char == b'\r':
                    self.content += self.curdata
                    self.newstep = True
                elif char == b'\n' and self.newstep:
                    self.newstep = False
                    self.curdata = b''
                    self.step = 6
                else:
                    self.curdata += char
        if (last or not self.stream) and self.chunked and not self.allchunks:
            raise IncompleteChunkedRequest
        elif (last or not self.stream) and not self.chunked and\
             int(self.headers.get(b'Content-Length', [0])[0]) !=\
             len(self.content):
                raise IncompleteRequest
        if last or not self.stream:
            self.step = 0
            self.curdata = b''
            self.chunked = False
            self.contentlen = 0
            self.newstep = False
            self.allchunks = False
            self.stream = False
        if self.allchunks or last:
            return False
        else:
            return True
    
    def __str__(self):
        self.headers[b'Host'] = [bytes('{}:{}'.format(self.url.domain,
                                                      self.url.port),
                                       'utf-8')]
        contentlength = len(self.content)
        if contentlength:
            self.headers[b'Content-Length'] = [contentlength]
        elif b'Content-Length' in self.headers:
            del self.headers[b'Content-Length']
        headersstring = b''
        if self.headers:
            for name, values in self.headers.items():
                for value in values:
                    headersstring += name + b': ' + value + b'\r\n'
        pathstring = b'/' + b'/'.join(bytes(i, 'utf-8') for i in self.url.path)
        request_string = self.method + b' ' + pathstring + b' ' + \
                         self.version + b'\r\n' + headersstring + \
                         b'\r\n' + self.content + b'\r\n\r\n'
        encoding = 'utf-8'
        if b'Content-Type' in self.headers:
            if b';' in self.headers[b'Content-Type']:
                encoding = str(self.headers[b'Content-Type'].split('=')[-1],
                               'utf-8')
        return str(request_string, encoding)

    def __iter__(self):
        self.index = -1
        return self

    def __next__(self):
        string = str(self)
        self.index += 1
        if self.index < len(string):
            return ord(string[self.index])
        else:
            raise StopIteration

class Response:
    def __init__(self, source=b'', version=b'HTTP/1.1',
                 status=[100, b'Continue'], headers={}, content=b'',
                 stream=False):
        self.version = version
        self.status = status
        self.headers = headers
        self.content = content
        self.step = 0
        self.curdata = b''
        self.chunked = False
        self.contentlen = 0
        self.newstep = False
        self.allchunks = False
        self.stream = stream
        self.feed(source)
            
    def feed(self, source, last=False):
        for char in (bytes(i) for i in source):
##            print('char', str(char))
##            print('step', self.step)
            if self.step == 0:
                # Get version
                if char == b' ':
                    self.version = self.curdata
                    self.curdata = b''
                    self.step = 1
                else:
                    self.curdata += char
            elif self.step == 1:
                # Get status code
                if char == b' ':
                    if self.curdata == b'100':
                        raise ContinueRequest
                    self.status[0] = int(self.curdata)
                    self.curdata = b''
                    self.step = 2
                else:
                    self.curdata += char
            elif self.step == 2:
                # Get status message
                if char == b'\r':
                    self.status[1] = self.curdata
                    self.curdata = b''
                elif char == b'\n':
                    self.step = 3
                else:
                    self.curdata += char
            elif self.step == 3:
                # Get headers name
                if char == b'\r' and not self.curdata:
                    continue
                elif char == b'\n' and not self.curdata:
                    if self.chunked:
                        self.step = 6
                    else:
                        self.step = 5
                elif char == b':':
                    self.curdata = [self.curdata, b'']
                    self.step = 4
##                    print(self.step)
                else:
                    self.curdata += char
            elif self.step == 4:
                # Get headers value
                if char == b'\r':
                    header = self.curdata[0].strip()
                    value = self.curdata[1].strip()
                    if header in self.headers:
                        self.headers[header].append(value)
                    else:
                        self.headers[header] = [value]
                    self.curdata = b''
                    if header == b'Location' and\
                       self.status[0] in range(300, 400):
                        raise Redirect(value)
##                    print(self.status)
                    if (header, value) ==\
                       (b'Transfer-Encoding', b'chunked'):
                        self.chunked = True
                elif char == b'\n':
                    self.step = 3
                else:
                    self.curdata[1] += char
            elif self.step == 5:
                # Get content
                if b'Content-Length' in self.headers:
                    if len(self.content) ==\
                       int(self.headers[b'Content-Length']):
                        last = True
                        break
                self.content += char
            elif self.step == 6:
                if char == b'\r' and not self.curdata:
                    break
                elif char in [b';', b'\r']:
                    self.newstep = True
                    chunklen = int(self.curdata, 16)
                    if chunklen:
                        self.contentlen += chunklen
                    else:
                        self.allchunks = True
                        self.step = 3
                    self.curdata = b''
                elif char == b'\n' and self.newstep:
                    self.newstep = False
                    self.step = 7
                else:
                    self.curdata += char
            elif self.step == 7:
                if char == b'\r':
                    self.content += self.curdata
                    self.newstep = True
                elif char == b'\n' and self.newstep:
                    self.newstep = False
                    self.curdata = b''
                    self.step = 6
                else:
                    self.curdata += char
        if (last or not self.stream) and self.chunked and not self.allchunks:
            raise IncompleteChunkedRequest
        elif (last or not self.stream) and not self.chunked and\
             int(self.headers.get(b'Content-Length', [0])[0]) !=\
             len(self.content):
                raise IncompleteRequest
        if last or not self.stream:
            self.step = 0
            self.curdata = b''
            self.chunked = False
            self.contentlen = 0
            self.newstep = False
            self.allchunks = False
            self.stream = False
        if self.allchunks or last:
            return False
        else:
            return True
                    
    def __str__(self):
        contentlength = len(self.content)
        if contentlength:
            self.headers[b'Content-Length'] = [contentlength]
        elif b'Content-Length' in self.headers:
            del self.headers[b'Content-Length']
        headersstring = b''
        if self.headers:
            for name, values in self.headers.items():
                for value in values:
                    headersstring += name + b': ' + value + b'\r\n'
        statusstring = bytes(str(self.status[0]), 'utf-8') + b' ' +\
                       self.status[1]
        request_string = self.version + b' ' + statusstring + b' ' + \
                         b'\r\n' + headersstring + \
                         b'\r\n' + self.content + b'\r\n\r\n'
        encoding = 'utf-8'
        if b'Content-Type' in self.headers:
            if b';' in self.headers[b'Content-Type']:
                encoding = str(self.headers[b'Content-Type'].split('=')[-1],
                               'utf-8')
        return str(request_string, encoding)
